﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.AspNetCore.Mvc;
using SimpleNugetServer.Models;

namespace SimpleNugetServer.Controllers
{
    [Route("api/v2")]
    public class IndexController : Controller
    {
        private INugetPackageSource _PackageSource;

        public IndexController(INugetPackageSource packageSource)
        {
            _PackageSource = packageSource;
        }

        public IActionResult Index()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "$Edmx/V2/Index.xml");
            var content = System.IO.File.ReadAllText(path);
            content = content.Replace("{baseUri}", $"{Request.Scheme}://{Request.Host}/api/v2");

            return Content(content, "text/xml", Encoding.UTF8);
        }

        [Route("$metadata")]
        public IActionResult Metadata()
        {
            var path = Path.Combine(Environment.CurrentDirectory, "$Edmx/V2/Metadata.xml");
            var content = System.IO.File.ReadAllText(path);
            return Content(content, "text/xml", Encoding.UTF8);
        }

        [Route("Search()")]
        public void Search()
        {
            var parameters = GetSearchParametersFromCurrentRequest();
            var result = _PackageSource.Find(parameters);
            ResponseFeed(result);
        }

        [Route("FindPackagesById()")]
        public void FindPackagesById(string id)
        {
            id = id.Trim(new char[] { '\'' });
            if (string.IsNullOrWhiteSpace(id))
            {
                Response.StatusCode = 404;
                return;
            }

            var result = _PackageSource.FindPackagesById(id);
            ResponseFeed(result);
        }

        /// <summary>
        /// 推送
        /// </summary>
        [HttpPut]
        [Route("package")]
        public void Package()
        {
            var nugetApiKey = Request.Headers["X-NuGet-ApiKey"][0];
            var nugetClientVersion = Request.Headers["X-NuGet-Client-Version"][0];
            var nugetSessionId = Request.Headers["X-NuGet-Session-Id"][0];

            if (Request.Form.Files.Count > 0)
            {
                foreach (var file in this.Request.Form.Files)
                {
                    using (var stream = file.OpenReadStream())
                    {
                        _PackageSource.PutPackage(nugetApiKey, stream);
                    }
                }
            }
            else
            {
                throw new NotSupportedException();
            }
        }

        /// <summary>
        /// 下载        
        /// </summary>
        [Route("package/{id}/{version}")]
        public void Package(string id, string version)
        {
            var nuspec = _PackageSource.FindPackagesById(id, version);
            if (nuspec == null || !System.IO.File.Exists(nuspec.Path))
            {
                Response.StatusCode = 404;
                return;
            }

            Response.StatusCode = 200;
            Response.ContentType = "application/octet-stream";
            using (FileStream fs = new FileStream(nuspec.Path, FileMode.Open, FileAccess.Read))
            {
                var buffer = new byte[8092];
                while (true)
                {
                    var byteTransfered = fs.Read(buffer, 0, buffer.Length);
                    if (byteTransfered == 0) break;

                    Response.Body.Write(buffer, 0, byteTransfered);
                }
            }
        }

        QueryParameters GetSearchParametersFromCurrentRequest()
        {
            var parameters = new QueryParameters();
            if (Request.Query.ContainsKey("$filter") && Request.Query["$filter"].Count > 0)
            {
                var filter = Request.Query["$filter"][0];
                if (filter.StartsWith("'") && filter.EndsWith("'"))
                {
                    parameters.SearchTerm = filter.Trim('\'');
                }
                else
                {
                    if (filter == "IsLatestVersion")
                    {
                        parameters.IsLatestVersion = true;
                    }
                    else if (filter == "IsAbsoluteLatestVersion")
                    {
                        parameters.IsAbsoluteLatestVersion = true;
                    }
                }
            }

            if (Request.Query.ContainsKey("targetFramework") && Request.Query["targetFramework"].Count > 0)
            {
                parameters.TargetFramework = Request.Query["targetFramework"][0].Trim('\'');
            }

            if (Request.Query.ContainsKey("$orderBy") && Request.Query["$orderBy"].Count > 0)
            {
                var orderBy = Request.Query["$orderBy"][0].Split(' ');
                parameters.OrderBy = orderBy[0];
                if (orderBy.Length > 1 && orderBy[1] == "desc")
                {
                    parameters.OrderByDescending = true;
                }
            }

            if (Request.Query.ContainsKey("$skip") && Request.Query["$skip"].Count > 0)
            {
                parameters.Skip = Convert.ToInt32(Request.Query["$skip"][0]);
            }

            if (Request.Query.ContainsKey("$top") && Request.Query["$top"].Count > 0)
            {
                parameters.Take = Convert.ToInt32(Request.Query["$top"][0]);
            }
            else
            {
                parameters.Take = 30;
            }

            if (Request.Query.ContainsKey("$select") && Request.Query["$select"].Count > 0)
            {
                parameters.SelectedFields = Request.Query["$select"][0].Split(',');
            }

            if (Request.Query.ContainsKey("searchTerm") && Request.Query["searchTerm"].Count > 0)
            {
                parameters.SearchTerm = Request.Query["searchTerm"][0].Trim('\'');
            }

            return parameters;
        }


        private void ResponseFeed(IEnumerable<Nuspec> list)
        {
            string baseUrl = string.Format("{0}://{1}/api/v2/", Request.Scheme, Request.Host);

            var doc = new XElement(
                XmlNamespaces.xmlns + "feed",
                new XAttribute("xmlns", XmlNamespaces.xmlns),
                new XAttribute(XNamespace.Xmlns + "base", XmlNamespaces.@base),
                new XAttribute(XNamespace.Xmlns + "d", XmlNamespaces.d),
                new XAttribute(XNamespace.Xmlns + "m", XmlNamespaces.m),
                new XAttribute(XNamespace.Xmlns + "georss", XmlNamespaces.georss),
                new XAttribute(XNamespace.Xmlns + "gml", XmlNamespaces.gml));

            doc.Add(new XElement(XmlNamespaces.m + "count", list.Count()));
            doc.Add(new XElement(XmlNamespaces.xmlns + "id", "http://schemas.datacontract.org/2004/07/"));
            doc.Add(new XElement(XmlNamespaces.xmlns + "title", ""));
            doc.Add(new XElement(XmlNamespaces.xmlns + "updated", DateTime.UtcNow));
            doc.Add(new XElement(
                XmlNamespaces.xmlns + "link",
                new XAttribute("rel", "self"),
                new XAttribute("href", baseUrl + "Packages")));

            foreach (var spec in list)
            {
                var entry = new XElement(XmlNamespaces.xmlns + "entry");
                entry.Add(new XElement(XmlNamespaces.xmlns + "id", string.Format("{0}Packages(Id='{1}',Version='{2}')", baseUrl, spec.Metadata.Id, spec.Metadata.Version)));
                entry.Add(new XElement(XmlNamespaces.xmlns + "title", new XAttribute("type", "text"), new XText(spec.Metadata.Id)));
                entry.Add(new XElement(
                    XmlNamespaces.xmlns + "content",
                    new XAttribute("type", "application/zip"),
                    new XAttribute("src", string.Format("{0}package/{1}/{2}", baseUrl, spec.Metadata.Id, spec.Metadata.Version))));

                entry.Add(new XElement(
                    XmlNamespaces.m + "properties",
                    new XElement(XmlNamespaces.d + "Id", spec.Metadata.Id),
                    new XElement(XmlNamespaces.d + "Version", spec.Metadata.Version),
                    new XElement(XmlNamespaces.d + "Authors", spec.Metadata.Authors),
                    new XElement(XmlNamespaces.d + "Copyright", spec.Metadata.Copyright),
                    new XElement(XmlNamespaces.d + "Dependencies", NugetPackageSource.CreateDependencyString(spec.Metadata.DependencySets)),
                    new XElement(XmlNamespaces.d + "Description", spec.Metadata.Description),
                    new XElement(XmlNamespaces.d + "IconUrl", spec.Metadata.IconUrl, new XAttribute(XmlNamespaces.m + "null", "true")),
                    new XElement(XmlNamespaces.d + "IsPrerelease", spec.IsPrerelease(), new XAttribute(XmlNamespaces.m + "type", "Edm.Boolean")),
                    new XElement(XmlNamespaces.d + "Language", spec.Metadata.Language, new XAttribute(XmlNamespaces.m + "null", "true")),
                    new XElement(XmlNamespaces.d + "PackageHash", spec.Hash),
                    new XElement(XmlNamespaces.d + "PackageHashAlgorithm", "SHA512"),
                    new XElement(XmlNamespaces.d + "PackageSize", spec.Size, new XAttribute(XmlNamespaces.m + "type", "Edm.Int64")),
                    new XElement(XmlNamespaces.d + "ProjectUrl", spec.Metadata.ProjectUrl),
                    new XElement(XmlNamespaces.d + "ReleaseNotes", spec.Metadata.ReleaseNotes),
                    new XElement(XmlNamespaces.d + "RequireLicenseAcceptance", spec.Metadata.RequireLicenseAcceptance, new XAttribute(XmlNamespaces.m + "type", "Edm.Boolean")),
                    new XElement(XmlNamespaces.d + "Summary", spec.Metadata.Summary),
                    new XElement(XmlNamespaces.d + "Tags", spec.Metadata.Tags),
                    new XElement(XmlNamespaces.d + "Title", spec.Metadata.Title),
                    new XElement(XmlNamespaces.d + "MinClientVersion", spec.Metadata.MinClientVersion, new XAttribute(XmlNamespaces.m + "null", "true")),
                    new XElement(XmlNamespaces.d + "LicenseUrl", spec.Metadata.LicenseUrl)
                    ));

                doc.Add(entry);
            }

            Response.ContentType = "text/xml";
            doc.Save(this.Response.Body);
        }

        
    }
}
